#!/usr/bin/python2.1
# Copyright (C) 2000-2001 The OpenRPG Project
#
# openrpg-dev@lists.sourceforge.net
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
# --
#
# File: meta_server_lib.py
# Author: Chris Davis
# Maintainer:
# Version:
#   $Id: meta_server_lib.py,v 1.40 2007/04/04 01:18:42 digitalxero Exp $
#
# Description: A collection of functions to communicate with the meta server.
#


#added debug flag for meta messages to cut console server spam --Snowdog

from __future__ import with_statement

META_DEBUG = False

import time
import sys
import random
import traceback
import re
import urllib, urllib2
from threading import *

from orpg.orpg_version import PROTOCOL_VERSION
from orpg.orpg_xml import *
import orpg.minidom # WTF?
from orpg.chat.chat_util import strip_html
from orpg.dirpath import dir_struct
from orpg.tools.validate import validate
from orpg.tools.settings import settings
from orpg.tools.orpg_log import logger
from orpg.tools.decorators import deprecated

from orpg.external.etree.ElementTree import ElementTree, Element
from orpg.external.etree.ElementTree import fromstring, tostring

metacache_lock = RLock()

def get_server_dom(data=None, path=None):
    if path == None:
        path = getMetaServerBaseURL()

    etree = ElementTree()

    # POST the data
    logger.debug("", META_DEBUG)
    logger.debug("Sending the following POST info to Meta at " + path + ":",
                 META_DEBUG)
    logger.debug("==========================================", META_DEBUG)
    logger.debug(data, META_DEBUG)
    logger.debug("", META_DEBUG)

    try:
        f = urllib.urlopen(path, data)
        etree.parse(f)
    finally:
        f.close()

    logger.debug("", META_DEBUG)
    logger.debug("Got this string from the Meta at " + path + ":",
                 META_DEBUG)
    logger.debug("==========================================", META_DEBUG)
    logger.debug(tostring(etree.getroot()), META_DEBUG)
    logger.debug("", META_DEBUG)

    return etree

@deprecated
def post_server_data(name, realHostName=None):
    if realHostName:
        data = urllib.urlencode({"server_data[name]": name,
                                  "server_data[version]": PROTOCOL_VERSION,
                                  "act": "new",
                                  "REMOTE_ADDR": realHostName})

    else:
        logger.debug("Letting meta server decide the hostname to list...",
                     META_DEBUG)
        data = urllib.urlencode({"server_data[name]": name,
                                  "server_data[version]": PROTOCOL_VERSION,
                                  "act": "new"})

    xml_dom = get_server_dom(data, "http://openrpg.sf.net/openrpg_servers.php")
    ret_val = int(xml_dom.getAttribute("id"))
    return ret_val

@deprecated
def post_failed_connection(id, meta=None, address=None, port=None):
    return 0

@deprecated
def remove_server(id):
    data = urllib.urlencode({"id": id, "act": "del"});
    xml_dom = get_server_dom(data)
    ret_val = int(xml_dom.getAttribute("return"))
    return ret_val

def byStartAttribute(first, second):
    first_start = int(first.get("start", 0))
    second_start = int(second.get("start", 0))

    return cmp(first_start, second_start)

def byNameAttribute(first,second):
    first_name = str(first.get("name"), "").lower()
    second_name = str(second.get("name", "")).lower()

    return cmp(first_name, second_name)

def get_server_list(versions=None, sort_by="start"):
    data = urllib.urlencode({"version": PROTOCOL_VERSION,"ports": "%"})
    all_metas = getMetaServers(versions, 1)
    base_meta = getMetaServerBaseURL()

    return_hash = {}

    for meta in all_metas:
        bad_meta = 0
        try:
            etree = get_server_dom(data=data, path=meta)
        except:
            bad_meta = 1

        if bad_meta:
            continue

        if base_meta == meta:
            updateMetaCache(etree)

    #  Create a servers element
    return_dom = ElementTree(Element("servers"))

    #  get the nodes stored in return_hash
    sorter_func = byStartAttribute if sort_by == 'start' else byNameAttribute
    return_list = sorted(etree.getroot().findall('server'), sorter_func)

    #  Add each node to the DOM
    for n in return_list:
        return_dom.getroot().append(n)

    return return_dom

def updateMetaCache(etree):
    try:
        logger.debug("Updating Meta Server Cache", META_DEBUG)
        metaservers = etree.find('metaservers')   # pull out the metaservers bit
        if metaservers is None:
            cmetalist = getRawMetaList()
            etree = get_server_dom(cmetalist[0])
            metaservers = metaservers = etree.find('metaservers')

        authoritative = metaservers.get('auth')
        logger.debug("  Authoritive Meta: " + str(authoritative), META_DEBUG)
        metas = metaservers.findall("meta")

        try:
            metacache_lock.acquire()
            with open(dir_struct["user"] + "metaservers.cache","w") as ini:
                for meta in metas:
                    logger.debug("   Writing: " + str(meta.get('path')),
                                 META_DEBUG)
                    to_file = [str(meta.get('path')), ' ',
                               str(meta.get('versions')), '\n']
                    ini.write(''.join(to_file))
        finally:
            metacache_lock.release()

    except Exception, e:
        logger.exception(traceback.print_exc())

def getRawMetaList():
    try:
        try:
            metacache_lock.acquire()
            #  Read in the metas
            validate.config_file("metaservers.cache","metaservers.cache")
            with open(dir_struct["user"]+"metaservers.cache","r") as ini:
                metas = ini.readlines()

            return metas
        finally:
            metacache_lock.release()
    except Exception, e:
        logger.exception(traceback.print_exc())
        return []

def getMetaServers(versions=None, pick_random=0):
    meta_names = []

    if(versions):
        try:
            metas = getRawMetaList()
            for meta in metas:
                words = meta.split()

                success = False

                for version in versions:
                    if version in words[1:]:
                        success = True

                if success:
                    meta_names.append(words[0])
                    logger.debug("adding metaserver " + meta, META_DEBUG)

            if not meta_names:
                default_meta = getMetaServerBaseURL()
                meta_names.append(default_meta)
            elif pick_random:
                logger.debug("choosing random meta from: " + str(meta_names),
                             META_DEBUG)
                i = int(random.uniform(0,len(meta_names)))

                meta_names = [meta_names[i]]
                logger.debug("using: " + str(meta_names), META_DEBUG)
            else:
                logger.debug("using all metas: " + str(meta_names), META_DEBUG)
            return meta_names
        except Exception,e:
            logger.exception(traceback.print_exc())
            return []
    else:
        url = getMetaServerBaseURL()
        meta_names.append(url)
        return meta_names

def getMetaServerBaseURL():
    # get meta server URL
    url = settings.get('MetaServerBaseURL',
                       'http://orpgmeta.appspot.com')

    if url == 'http://www.openrpg.com/openrpg_servers.php':
        url = 'http://orpgmeta.appspot.com'
        settings.set('MetaServerBaseURL', url)

    return url

"""
#  Beginning of Class registerThread
#
#  A Class to Manage Registration with the Meta2
#  Create an instance and call it's start() method
#  if you want to be (and stay) registered.  This class
#  will take care of registering and re-registering as
#  often as necessary to stay in the Meta list.
#
#  You may call register() yourself if you wish to change your
#  server's name.  It will immediately update the Meta.  There
#  is no need to unregister first.
#
#  Call unregister() when you no longer want to be registered.
#  This will result in the registerThread dying after
#  attempting to immediately remove itself from the Meta.
#
#  If you need to become registered again after that, you
#  must create a new instance of class registerThread.  Don't
#  just try to call register() on the old, dead thread class.
"""

class registerThread(Thread):
    def __init__(self, name=None, realHostName=None, num_users="0",
                 MetaPath=None, port=6774, register_callback=None):

        Thread.__init__(self, name="registerThread")

        self.rlock = RLock()
        self.die_event = Event()

        if name:
            self.name = strip_html(name)
        else:
            self.name = "Unnamed server"

        self.num_users = num_users
        self.realHostName = realHostName
        self.id = "0"
        self.cookie = "0"
        self.interval = 0
        self.destroy = 0
        self.port = str(port)
        self.register_callback = register_callback

        if MetaPath == None:
            self.path = getMetaServerBaseURL()
        else:
            self.path = MetaPath

    def getIdAndCookie(self):
        return self.id, self.cookie

    def TestDeleteStatus(self):
        try:
            self.rlock.acquire()
            return self.die_event.isSet()
        finally:
            self.rlock.release()

    def Delete(self):
        try:
            self.rlock.acquire()
            self.die_event.set()
        finally:
            self.rlock.release()

    def run(self):
        while(not self.TestDeleteStatus()):
            self.register(self.name, self.realHostName, self.num_users)
            logger.debug("Sent Registration Data", META_DEBUG)

            try:
                self.rlock.acquire()

                if self.interval >= 2:
                    self.interval -= 1
                else:
                    self.interval = .5

            finally:
                self.rlock.release()

            #  Wait interval minutes for a command to die
            die_signal = self.die_event.wait(self.interval*60)

    def unregister(self):
        """
        This method can (I hope) be called from both within the thread
        and from other threads.  It will attempt to unregister this
        server from the Meta database
        When this is either accomplished or has been tried hard enough
        (after which it just makes sense to let the Meta remove the
        entry itself when we don't re-register using this id),
        this method will either cause the thread to immediately die
        (if called from this thread's context) or set the Destroy flag
        (if called from the main thread), a positive test for which will cause
        the code in Entry() to exit() when the thread wakes up and
        checks TestDeleteStatus().
        lock the critical section.  The unlock will
        automatically occur at the end of the function in the finally clause
        """
        try:
            self.rlock.acquire()
            if not self.isAlive():
                return 1

            #  Do the actual unregistering here
            data = urllib.urlencode({"server_data[id]": self.id,
                                        "server_data[cookie]": self.cookie,
                                        "server_data[version]": PROTOCOL_VERSION,
                                        "act":"unregister"})
            try:
                etree = get_server_dom(data=data, path=self.path)
                if etree.getroot().get("errmsg"):
                    logger.general("Error durring unregistration:  " +\
                                   etree.getroot().get("errmsg"))
            except:
                logger.debug("Problem talking to Meta. Will go ahead and "
                             "die, letting Meta remove us.", META_DEBUG)

            self.Delete()

            return 0
        finally:
            self.rlock.release()

    def register(self, name=None, realHostName=None, num_users=None):
        """
        Designed to handle the registration, both new and repeated.

        It is intended to be called once every interval
        (or interval - delta) minutes.

        lock the critical section.  The unlock will
        automatically occur at the end of the function in the finally clause
        """
        try:
            self.rlock.acquire()
            if not self.isAlive():
                return 1
            #  Set the server's attibutes, if specified.
            if name:
                self.name = name

            if num_users != None:
                self.num_users = num_users

            if realHostName:
                self.realHostName = realHostName

            # build POST data
            if self.realHostName:
                data = urllib.urlencode({"server_data[id]": self.id,
                                    "server_data[cookie]": self.cookie,
                                    "server_data[name]": self.name,
                                    "server_data[port]": self.port,
                                    "server_data[version]": PROTOCOL_VERSION,
                                    "server_data[num_users]": self.num_users,
                                    "act": "register",
                                    "server_data[address]": self.realHostName})
            else:
                logger.debug("Letting meta server decide the "
                             "hostname to list...", META_DEBUG)
                data = urllib.urlencode( {"server_data[id]":self.id,
                                        "server_data[cookie]":self.cookie,
                                        "server_data[name]":self.name,
                                        "server_data[port]":self.port,
                                        "server_data[version]":PROTOCOL_VERSION,
                                        "server_data[num_users]":self.num_users,
                                        "act":"register"} )
            try:
                etree = get_server_dom(data=data,path=self.path)
            except:
                logger.debug("Problem talking to server. "
                             "Setting interval for retry ...", META_DEBUG)
                logger.debug(data, META_DEBUG)
                self.interval = 0

                return 0

            if etree is not None:
                if etree.getroot().get("errmsg"):
                    logger.general("Error durring registration: " +\
                                   etree.getroot().get("errmsg"))
                    logger.debug(data, META_DEBUG)

                try:
                    self.interval = int(etree.getroot().get("interval"))
                    self.id = etree.getroot().get("id")
                    self.cookie = etree.getroot().get("cookie")
                    if not etree.getroot().get("errmsg"):
                        updateMetaCache(etree)
                except:
                    logger.debug("OOPS!  Is the Meta okay?  It should be "
                                 "returning an id, cookie, and interval.",
                                 META_DEBUG)
                    logger.debug("Check to see what it really "
                                 "returned.\n", META_DEBUG)

            else:
                logger.general("Error - no DOM constructed from Meta message!")
            return 0
        finally:
            self.rlock.release()
